package mbsl.util;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.eclipse.swt.widgets.Display;

/**
 * A simple delayer which takes a Runnable, and delays a number of milliseconds before executing it.
 * <p>
 * Note that interrupt() will simply reset the delay timer, not cancel execution.  When the Delayer
 * is no longer needed, it must be disposed.
 * <p>
 * Extending case-specific classes include:<br>
 * {@link SWTDelayer}
 */
public class Delayer {
	private Runnable mRunnable;
	private int mDelay;
	private ExecutorService mThreadPool;
	private DelayThread mDelayThread;
	
	/**
	 * Creates a Delayer which will execute the Runnable after a number of milliseconds.
	 * @param pRunnable The Runnable to execute.
	 * @param pDelay The number of milliseconds to wait before executing the Runnable.
	 */
	public Delayer(Runnable pRunnable, int pDelay) {
		mRunnable = pRunnable;
		mDelay = pDelay;
		mThreadPool = Executors.newFixedThreadPool(1);
		mDelayThread = null;
	}
	/**
	 * Starts the delay thread.
	 * <p>
	 * After the amount of milliseconds set in the constructor, the attached Runnable will
	 * be run, unless interrupted.
	 * @see #interrupt()
	 */
	public void start() {
		if (mDelayThread == null) {
			mDelayThread = new DelayThread();
			
			mThreadPool.execute(mDelayThread);
		}
	}
	
	private class DelayThread implements Runnable {
		public synchronized void run() {
			mRun = true;
			
			try {
				while (mRun) {
					mRun = false;

					wait(mDelay);
				}
			} catch (InterruptedException e) {}
			
			clean();
			execute();
		}
		
		private synchronized void interrupt() {
			mRun = true;
			
			notify();
		}
		
		private boolean mRun;
	}
	/** Interrupts the delay thread, reseting its timer to zero. */
	public void interrupt() {
		if (mDelayThread != null)
			mDelayThread.interrupt();
	}
	/** Returns true if the delay thread is currently active. */
	public boolean isStarted() {
		return mDelayThread != null;
	}
	private void clean() {
		mDelayThread = null;
	}
	/** Calls the run() method on the attached Runnable. */
	protected void execute() {
		mRunnable.run();
	}
	/** Shuts down the internal Thread Pool. Further calls to this instance may cause exceptions. */
	public void dispose() {
		mThreadPool.shutdown();
	}
	/**
	 * A simple delayer which takes a Runnable and delays a number of milliseconds before executing it on the SWT Event Thread.
	 * <p>
	 * Note that interrupt() will simply reset the delay timer, not cancel execution.
	 */
	public static class SWTDelayer extends Delayer {
		private Display mDisplay;
		/**
		 * Creates a Delayer which will execute the Runnable on the SWT Event Thread after a number of milliseconds.
		 * @param pRunnable The Runnable to execute.
		 * @param pDelay The number of milliseconds to wait before executing the Runnable.
		 * @param pDisplay The Display owning the SWT Event Thread to execute on.
		 */
		public SWTDelayer(Runnable pRunnable, int pDelay, Display pDisplay) {
			super(pRunnable, pDelay);
			
			mDisplay = pDisplay;
		}
		@Override
		protected void execute() {
			SWTUtils.exec(mDisplay, new Runnable() {
				public void run() {
					SWTDelayer.super.execute();
				}
			});
		}
	}
}
